package com.yjb.tenet.boot.framework.web.handler;

import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.exception.NotPermissionException;
import com.yjb.tenet.boot.common.exception.CustomException;
import com.yjb.tenet.boot.common.exception.enums.ErrorCodeEnum;
import com.yjb.tenet.boot.common.pojo.ResponseResult;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.servlet.NoHandlerFoundException;

import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;

/**
 * @Description 全局异常处理器，将 Exception 翻译成 ResponseResult + 对应的异常编号
 * @Author yin.jinbiao
 * @Date 2021/10/12 13:35
 * @Version 1.0
 */
@RestControllerAdvice
@AllArgsConstructor
@Slf4j
public class GlobalExceptionHandler {

	/**
	 * 处理 Sa-token 未登录异常
	 * @param ex
	 * @return
	 */
	@ExceptionHandler(value = NotLoginException.class)
	public ResponseResult<?> notLoginException(NotLoginException ex) {
		log.warn("[notLoginExceptionExceptionHandler]", ex);
		return ResponseResult.error(ErrorCodeEnum.UNAUTHORIZED.getCode(), String.format("登录状态失效"));
	}

	/**
	 * 权限不足异常
	 * @param ex
	 * @return
	 */
	@ExceptionHandler(value = NotPermissionException.class)
	public ResponseResult<?> NotPermissionException(NotPermissionException ex) {
		log.warn("[notPermissionExceptionHandler]", ex);
		return ResponseResult.error(ErrorCodeEnum.PERMISSION_DENY.getCode(), ex.getMessage());
	}

    /**
     * 处理 SpringMVC 请求参数缺失
     *
     * 例如说，接口上设置了 @RequestParam("xx") 参数，结果并未传递 xx 参数
     */
    @ExceptionHandler(value = MissingServletRequestParameterException.class)
    public ResponseResult<?> missingServletRequestParameterExceptionHandler(MissingServletRequestParameterException ex) {
        log.warn("[missingServletRequestParameterExceptionHandler]", ex);
        return ResponseResult.error(ErrorCodeEnum.PARAM_MISS.getCode(), String.format("请求参数缺失:%s", ex.getParameterName()));
    }

    /**
     * 处理 SpringMVC 请求参数类型错误
     *
     * 例如说，接口上设置了 @RequestParam("xx") 参数为 Integer，结果传递 xx 参数类型为 String
     */
    @ExceptionHandler(MethodArgumentTypeMismatchException.class)
    public ResponseResult<?> methodArgumentTypeMismatchExceptionHandler(MethodArgumentTypeMismatchException ex) {
        log.warn("[missingServletRequestParameterExceptionHandler]", ex);
        return ResponseResult.error(ErrorCodeEnum.PARAM_ILLEGAL.getCode(), String.format("请求参数类型错误:%s", ex.getMessage()));
    }

    /**
     * 处理 SpringMVC 参数校验不正确
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseResult<?> methodArgumentNotValidExceptionExceptionHandler(MethodArgumentNotValidException ex) {
        log.warn("[methodArgumentNotValidExceptionExceptionHandler]", ex);
        FieldError fieldError = ex.getBindingResult().getFieldError();
	    // 断言，避免告警
        assert fieldError != null;
        return ResponseResult.error(ErrorCodeEnum.PARAM_ILLEGAL.getCode(), String.format("请求参数不正确:%s", fieldError.getDefaultMessage()));
    }

    /**
     * 处理 SpringMVC 参数绑定不正确，本质上也是通过 Validator 校验
     */
    @ExceptionHandler(BindException.class)
    public ResponseResult<?> bindExceptionHandler(BindException ex) {
        log.warn("[handleBindException]", ex);
        FieldError fieldError = ex.getFieldError();
	    // 断言，避免告警
        assert fieldError != null;
        return ResponseResult.error(ErrorCodeEnum.PARAM_ILLEGAL.getCode(), String.format("请求参数不正确:%s", fieldError.getDefaultMessage()));
    }

    /**
     * 处理 Validator 校验不通过产生的异常
     */
    @ExceptionHandler(value = ConstraintViolationException.class)
    public ResponseResult<?> constraintViolationExceptionHandler(ConstraintViolationException ex) {
        log.warn("[constraintViolationExceptionHandler]", ex);
        ConstraintViolation<?> constraintViolation = ex.getConstraintViolations().iterator().next();
        return ResponseResult.error(ErrorCodeEnum.PARAM_ILLEGAL.getCode(), String.format("请求参数不正确:%s", constraintViolation.getMessage()));
    }

    /**
     * 处理 SpringMVC 请求地址不存在
     *
     * 注意，它需要设置如下两个配置项：
     * 1. spring.mvc.throw-exception-if-no-handler-found 为 true
     * 2. spring.mvc.static-path-pattern 为 /statics/**
     */
    @ExceptionHandler(NoHandlerFoundException.class)
    public ResponseResult<?> noHandlerFoundExceptionHandler(NoHandlerFoundException ex) {
        log.warn("[noHandlerFoundExceptionHandler]", ex);
        return ResponseResult.error(ErrorCodeEnum.PARAM_ILLEGAL.getCode(), String.format("请求地址不存在:%s", ex.getRequestURL()));
    }

    /**
     * 处理 SpringMVC 请求方法不正确
     *
     * 例如说，A 接口的方法为 GET 方式，结果请求方法为 POST 方式，导致不匹配
     */
    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    public ResponseResult<?> httpRequestMethodNotSupportedExceptionHandler(HttpRequestMethodNotSupportedException ex) {
        log.warn("[httpRequestMethodNotSupportedExceptionHandler]", ex);
        return ResponseResult.error(ErrorCodeEnum.PARAM_ILLEGAL.getCode(), String.format("请求方法不正确:%s", ex.getMessage()));
    }


    /**
     * 处理业务异常 CustomException
     *
     * 例如说，商品库存不足，用户手机号已存在。
     */
    @ExceptionHandler(value = CustomException.class)
    public ResponseResult<?> customExceptionHandler(CustomException ex) {
        log.info("[customExceptionHandler]", ex);
        return ResponseResult.error(ex.getErrorCode(), ex.getErrorMsg());
    }

    /**
     * 处理系统异常，兜底处理所有的一切
     */
    @ExceptionHandler(value = Exception.class)
    public ResponseEntity<?> defaultExceptionHandler(HttpServletRequest req, Throwable ex) {
        log.error("[defaultExceptionHandler]", ex);
        // 返回 ERROR ResponseResult
        return new ResponseEntity<>(ResponseResult.error(ErrorCodeEnum.UNKNOWN_ERROR.getCode(), ErrorCodeEnum.UNKNOWN_ERROR.getMsg()), HttpStatus.INTERNAL_SERVER_ERROR);
    }

}
